use std::{fs, path::Path};

use anyhow::Result;
use hdf5::{Dataset, types::VarLenUnicode};

pub fn read_label_file(label_file: &str) -> Result<Vec<(String, String)>> {
    let mut results = Vec::new();

    let label_content = fs::read_to_string(label_file)?;
    for line in label_content.lines() {
        let line_content: Vec<_> = line.split("\t").collect();
        if line_content.len() != 2 {
            eprintln!("invalid label line: {}", line);
            continue;
        }

        results.push((String::from(line_content[0]), String::from(line_content[1])));
    }

    Ok(results)
}

pub fn read_classes_file(classes_file: &str) -> Result<Vec<String>> {
    let mut classes = Vec::new();
    for line in fs::read_to_string(classes_file)?.lines() {
        classes.push(line.to_string());
    }
    Ok(classes)
}

pub fn read_classes_file_from_hdf5(hdf5_file: &str) -> Result<Vec<String>> {
    let file = hdf5::File::open(hdf5_file)?;

    // 读取类别信息
    let classes_dataset: Dataset = file.dataset("classes")?;
    let classes: Vec<String> = classes_dataset
        .read_raw::<VarLenUnicode>()?
        .into_iter()
        .map(|v| v.to_string())
        .collect();

    Ok(classes)
}

pub fn read_hdf5_classes_len(hdf5_file: &Path) -> Result<usize> {
    let file = hdf5::File::open(hdf5_file)?;
    let classes = file.dataset("classes")?;
    Ok(classes.size())
}

/// 把 labels 写入 txt，等价于 Python 的 write_txt
pub fn write_txt<P: AsRef<Path>>(path: P, lines: &[String], sep: &str) -> Result<()> {
    let data = lines.join(sep);
    fs::write(path, data)?;
    Ok(())
}

pub fn read_subdir(path: &Path) -> Vec<String> {
    let mut parts = Vec::new();
    for part in path.read_dir().expect(&format!(
        "Failed to read images {} directory",
        path.to_str().unwrap().to_string()
    )) {
        if let Ok(part) = part
            && part.file_type().unwrap().is_dir()
        {
            parts.push(part.path().to_str().unwrap().to_string());
        }
    }
    parts
}

// Otsu阈值算法实现
pub fn otsu_threshold(hist: &[u32; 256], total_pixels: u32) -> u32 {
    let mut sum = 0.0;
    for i in 0..256 {
        sum += i as f64 * hist[i] as f64;
    }

    let mut sum_background = 0.0;
    let mut weight_background = 0.0;
    let mut weight_foreground;

    let mut max_variance = 0.0;
    let mut threshold = 0;

    for i in 0..256 {
        weight_background += hist[i] as f64;
        if weight_background == 0.0 {
            continue;
        }

        weight_foreground = total_pixels as f64 - weight_background;
        if weight_foreground == 0.0 {
            break;
        }

        sum_background += (i as f64) * hist[i] as f64;
        let mean_background = sum_background / weight_background;
        let mean_foreground = (sum - sum_background) / weight_foreground;

        let variance =
            weight_background * weight_foreground * (mean_background - mean_foreground).powi(2);

        if variance > max_variance {
            max_variance = variance;
            threshold = i;
        }
    }

    threshold as u32
}
